/*
 * np_stats.h
 *
 *  Created on: Mar 24, 2011
 *      Author: jeff_zheng
 *   Copyright: NetPerform Technology
 */

#ifndef NP_STATS_H_
#define NP_STATS_H_

#ifdef __cplusplus
extern "C" {
#endif

#define NP_STAT_FIELD_PRINT_FORMAT_uint64 "lu"
#define NP_STAT_FIELD_PRINT_FORMAT_uint32 "u"

#define NP_STAT_FIELD_PRINT_FORMAT_int32  "d"

#define NP_STAT_FIELD_PRINT_FORMAT_int64  "ld"

#define NP_STAT_FIELD_PRINT_FORMAT_string  "s"

#define NP_STAT_FIELD_PRINT_FORMAT_float  "f"

#define NP_STAT_FIELD_PRINT_FORMAT_double  "lf"

#define NP_STAT_FIELD_PRINT_FORMAT(str,width,nametype) sprintf(str,"|%%-%u"NP_STAT_FIELD_PRINT_FORMAT_##nametype,width)

#define NP_STAT_DIS_HTBL_BITS 10
/**
 * @defgroup np_stats_acc np_stats_acc
 * @brief a quick stats accumulator
 * @ingroup np_common
 *
 * used to monitor a incremental value and create interval values from historical values
 * the accumulator can monitor a set of instance, and each instance can have multiple fields
 */

/*
 *
 */
typedef enum np_stats_acc_val_e
{
	NP_STATS_ACC_VAL_INTERVAL = 0,
	NP_STATS_ACC_VAL_CURRENT  = 1
}np_stats_acc_value;

typedef enum np_stats_acc_type_e
{
	NP_STATS_ACC_UNK = 0,
	NP_STATS_ACC_MAX = 1,
	NP_STATS_ACC_MIN = 2,
	NP_STATS_ACC_MEAN = 3,
	NP_STATS_ACC_SUM = 4,
	NP_STATS_ACC_LAST = 5,
	NP_STATS_ACC_DIS = 6
}np_stats_acc_type;

typedef struct np_stats_acc_unit_s
{
	uint8_t acc_data[8];
	uint8_t raw_data[8];
	uint64_t count;
}np_stats_acc_unit;

typedef struct np_stats_acc_distribute_s
{
	np_htbl*  dis_htbl;
	np_slist  dis_list;
}np_stats_acc_distribute;

typedef struct np_stats_acc_s
{
	np_stats_acc_value 	type;
	uint32_t 			instance_cnt;
	uint32_t 			field_cnt;
	np_stats_acc_type*	acc_types;
	np_stats_acc_distribute* acc_dis;
	np_stats_acc_unit*	acc_data;
	void* 				old_value;
	char* 				interval_init;
}np_stats_acc;

typedef struct np_stats_acc_dis_elem_s
{
	uint64_t value;
    uint64_t count;
    np_slist_node node;
}np_stats_acc_dis_elem;

/*
  斐波那契（Fibonacci）散列法
1，对于16位整数而言，这个乘数是40503
2，对于32位整数而言，这个乘数是 2654435769
                           4294967295
3，对于64位整数而言，这个乘数是11400714819323198485
                          18446744073709551615
index = (value * 2654435769) >> 28*/
static inline uint32_t np_stats_acc_dis_hash_uint64(const void* p_key)
{
	uint64_t tmp_key = *(uint64_t*)p_key;
	uint32_t value = (uint64_t)(tmp_key * 11400714819323198485UL) >> (64 - NP_STAT_DIS_HTBL_BITS);
	return value;
}

static inline int np_stats_acc_dis_cmp_key_uint64(const void* p_key1, const void* p_key2)
{
	if(*(uint64_t*)p_key1 > *(uint64_t*)p_key2)
	{
		return 1;
	}
	else if(*(uint64_t*)p_key1 < *(uint64_t*)p_key2)
	{
		return -1;
	}
	else
	{
		return 0;
	}
}

static inline void np_stats_acc_dis_clear(np_stats_acc_distribute* dis)
{
	np_htbl_destroy(dis->dis_htbl);
	np_slist_node* p_del = NULL;
	while(NULL != (p_del = np_slist_del_head(&dis->dis_list)))
	{
		np_stats_acc_dis_elem* del_elem = np_slist_entry(p_del,np_stats_acc_dis_elem,node);
		free(del_elem);
	}
}

static inline void np_stats_acc_dis_array_clear(np_stats_acc* p_acc,uint32_t instance_max,uint32_t field_max)
{
	uint32_t field_tmp;
	uint32_t instance_tmp;
	for(instance_tmp = 0;instance_tmp < instance_max; instance_tmp++)
	{
		uint32_t instance_end = instance_tmp * p_acc->field_cnt;
		for(field_tmp = 0;field_tmp < field_max;field_tmp++)
		{
			if(p_acc->acc_types[field_tmp] == NP_STATS_ACC_DIS)
			{
				np_stats_acc_distribute* persent_dis = p_acc->acc_dis + instance_end + field_tmp;
				np_stats_acc_dis_clear(persent_dis);
			}
		}
	}
}

static inline int np_stats_acc_dis_init(np_stats_acc* p_acc)
{
	uint32_t field_curr;
	uint32_t instance_curr;
	np_bool err_flag = np_false;
	for(instance_curr = 0;instance_curr < p_acc->instance_cnt; instance_curr++)
	{
		for(field_curr = 0;field_curr < p_acc->field_cnt;field_curr++)
		{
			if(p_acc->acc_types[field_curr] == NP_STATS_ACC_DIS)
			{
				np_stats_acc_distribute* persent_dis = p_acc->acc_dis + instance_curr * p_acc->field_cnt + field_curr;
				persent_dis->dis_htbl = np_htbl_create(NP_STAT_DIS_HTBL_BITS,np_stats_acc_dis_hash_uint64, np_stats_acc_dis_cmp_key_uint64,2048);
				NP_LOG("np_htbl_create");
				if(persent_dis->dis_htbl == NULL)
				{
					err_flag = np_true;
					NP_ERR("np_stats_acc_dis_init()  np_htbl_create failed");
					break;
				}
				np_slist_init(&persent_dis->dis_list);
			}
		}
		if(err_flag)
			break;
	}
	if(err_flag)
	{
		np_stats_acc_dis_array_clear(p_acc,instance_curr + 1,field_curr);
		NP_ERR("np_stats_acc_dis_init()  initial (np_stats_acc_distribute*) acc_dis failed");
		return -1;
	}
	return 0;
}

static inline int np_stats_acc_dis_insert_elem_uint64(np_stats_acc_distribute* p_dis,uint64_t new_value)
{
	np_htbl_node* p_node = np_htbl_get_node(p_dis->dis_htbl,&new_value);
	if(p_node == NULL)
	{
		np_stats_acc_dis_elem* p_elem = (np_stats_acc_dis_elem*)calloc(1,sizeof(np_stats_acc_dis_elem));
		p_elem->value = new_value;
		p_elem->count = 1;
		if(0 != np_htbl_insert(p_dis->dis_htbl, &p_elem->value,p_elem))
		{
			free(p_elem);
			return -1;
		}
		np_slist_add_head(&p_elem->node,&p_dis->dis_list);
	}
	else
	{
		((np_stats_acc_dis_elem*)(p_node->p_elem))->count++;
	}
	return 0;
}

static inline int np_stats_acc_dis_insert_elem_float(np_stats_acc_distribute* p_dis,float new_value){return 0;}
static inline int np_stats_acc_dis_insert_elem_double(np_stats_acc_distribute* p_dis,double new_value){return 0;}
static inline int np_stats_acc_dis_insert_elem_uint32(np_stats_acc_distribute* p_dis,uint32_t new_value){return 0;}
static inline int np_stats_acc_dis_insert_elem_int32(np_stats_acc_distribute* p_dis,int32_t new_value){return 0;}
static inline int np_stats_acc_dis_insert_elem_int64(np_stats_acc_distribute* p_dis,int64_t new_value){return 0;}

static inline int np_stats_acc_node_cmp(np_slist_node* p_node1, np_slist_node* p_node2)
{
	np_stats_acc_dis_elem* elem1= np_slist_entry(p_node1,np_stats_acc_dis_elem,node);
	np_stats_acc_dis_elem* elem2= np_slist_entry(p_node2,np_stats_acc_dis_elem,node);

	if(elem1->value > elem2->value)
	{
		return -1;
	}
	else if(elem1->value < elem2->value)
	{
		return 1;
	}
	else
	{
		return 0;
	}
}

static inline void np_stats_acc_dis_list_sort(np_stats_acc_distribute* p_dis)
{
	np_slist_qsort(&p_dis->dis_list,np_stats_acc_node_cmp);
}

static inline void np_stats_acc_print_dis(np_stats_acc_distribute* p_dis,uint16_t prnt_width,uint16_t line_count)
{
	np_stats_acc_dis_list_sort(p_dis);
	char fm_name[10] = {0};
	char fm_uint32[10] = {0};
	NP_STAT_FIELD_PRINT_FORMAT(fm_name,prnt_width,uint64);
	NP_STAT_FIELD_PRINT_FORMAT(fm_uint32,prnt_width,uint32);

	uint16_t bar_cnt = prnt_width * line_count;
	char* bar_str = (char *) malloc(bar_cnt + 1);
	memset(bar_str,'-',bar_cnt);
	bar_str[bar_cnt] = 0;

	uint16_t i = 0;
	np_slist_node* p_node = p_dis->dis_list.first;

	uint64_t* cnt_arry = (uint64_t*)malloc(line_count * sizeof(uint64_t));
	while(p_node != NULL)
	{
		np_stats_acc_dis_elem* p_elem = np_slist_entry(p_node,np_stats_acc_dis_elem,node);
		if(p_elem == NULL)
		{
			NP_ERR("data structure create failed");
			return ;
		}
		if(0 == i)
		{
			printf("%s\n",bar_str);
		}
		*(cnt_arry + i) = p_elem->count;
		printf(fm_name,p_elem->value);

		i++;

		if(i == line_count)
		{
			printf("\n");
			uint16_t j = 0;
			while(j < i)
			{
				printf(fm_name,cnt_arry[j]);
				j++;
			}
			printf("\n");
			i = 0;
		}
		p_node = p_node->next;
	}
	printf("\n");
	uint16_t j = 0;
	while(j < i)
	{
		printf(fm_name,cnt_arry[j]);
		j++;
	}
	printf("\n%s\n",bar_str);
	printf("\n");
	free(bar_str);
	free(cnt_arry);
}

static inline void np_stats_acc_print_dis_array(np_stats_acc* p_acc,char* field_name,np_bool flag,uint16_t prnt_width,uint16_t line_count)
{
	uint32_t instance_cnt;
	uint32_t field_cnt;
	for(instance_cnt = 0;instance_cnt < p_acc->instance_cnt;instance_cnt++)
	{
		uint32_t inst_end = instance_cnt * p_acc->field_cnt;
		for(field_cnt = 0;field_cnt < p_acc->field_cnt;field_cnt++)
		{
			if(p_acc->acc_types[field_cnt] == NP_STATS_ACC_DIS)
			{
				np_stats_acc_distribute* p_dis = p_acc->acc_dis + inst_end + field_cnt;
				np_stats_acc_print_dis(p_dis,prnt_width,line_count);
			}
		}
	}
}

/**
 * @ingroup np_stats_acc
 * @brief Uninitialize a stats accumulator
 *
 * @param[in] p_acc 	        struct to initialize
 * @return 0 for success, non-zero for Error
 *
 * Uninitialize a stats accumulator, free all resources.
 **/
static inline void np_stats_acc_destroy(np_stats_acc* p_acc)
{
	if(p_acc)
	{
		np_stats_acc_dis_array_clear(p_acc,p_acc->instance_cnt,p_acc->field_cnt);
		free(p_acc);
	}
}
/**
 * @ingroup np_stats_acc
 * @brief initialize a stats accumulator
 *
 * @param[in] p_acc 	        struct to initialize
 * @param[in] instance_count 	number of instances
 * @param[in] field_count 	    number of fields per instance
 * @return the created stats_acc obj, NULL for Error
 *
 **/
static inline np_stats_acc* np_stats_acc_create(np_stats_acc_value type, uint32_t instance_count,
		         np_stats_acc_type* field_acc_types, uint32_t field_count)
{
	uint32_t total_size = sizeof(np_stats_acc) +
						sizeof(np_stats_acc_type)*field_count +
						sizeof(uint64_t)*(instance_count*field_count*2) +
						sizeof(np_stats_acc_distribute)*field_count*instance_count+
						sizeof(np_stats_acc_unit)*(instance_count*field_count);
	np_stats_acc* p_acc = (np_stats_acc*)malloc(total_size);
	if(p_acc==NULL)
		return NULL;
	memset(p_acc, 0, total_size);
	p_acc->type = type;
	p_acc->instance_cnt = instance_count;
	p_acc->field_cnt = field_count;
	p_acc->old_value = NP_PTR_OFFSET(p_acc, sizeof(*p_acc), void);
	p_acc->acc_types = NP_PTR_OFFSET(p_acc->old_value,  (instance_count*field_count*sizeof(uint64_t)), np_stats_acc_type);
	memcpy(p_acc->acc_types, field_acc_types, sizeof(np_stats_acc_type)* field_count);

	p_acc->acc_dis = NP_PTR_OFFSET(p_acc->acc_types, sizeof(np_stats_acc_type)*(field_count), np_stats_acc_distribute);

	p_acc->acc_data = NP_PTR_OFFSET(p_acc->acc_dis, sizeof(np_stats_acc_distribute)*field_count*instance_count, np_stats_acc_unit);
	p_acc->interval_init = NP_PTR_OFFSET(p_acc->acc_data, (instance_count*field_count*sizeof(np_stats_acc_unit)), char);

	int ret;
	if(0 != (ret = np_stats_acc_dis_init(p_acc)))
	{
		NP_ERR("np_stats_acc_create()  call np_stats_acc_dis_init failed,return code : %d",ret);
		free(p_acc);
		return NULL;
	}
	return p_acc;
}


/**
 * @ingroup np_stats_acc
 * @brief set initial value for off field
 *
 * @param[in] p_acc 	        acc struct
 * @param[in] inst 				instance index
 * @param[in] field 	    	field index
 * @param[in] value 	    	initial value
 * @return 0 for success, non-zero for Error
 *
 * after initialization all fields are set to 0, but the user data may already have an existing value.
 * In such case, the caller should call this function once per instance per field with the existing value.
 **/
//static inline int np_stats_acc_set_initial_val(np_stats_acc* p_acc, uint32_t inst, uint32_t field, uint64_t value)
//{
////	*(p_acc->old_value+(inst*p_acc->field_cnt + field)) = value;
//	return 0;
//}

/**
 * @ingroup np_stats_acc
 * @brief set initial value for off field
 *
 * @param[in] p_acc 	        acc struct
 * @param[in] inst 				instance index
 * @param[in] field 	    	field index
 * @param[in] value 	    	new value to be pushed
 * @return 0 for success, non-zero for Error
 *
 * the caller should call this function periodically with a new value each time.
 * the accumulator will then use the new value to generate the value the caller needed.
 * In such case, the caller should call this function once per instance per field with the existing value.
 **/
//static inline uint64_t np_stats_acc_push_new(np_stats_acc* p_acc, uint32_t inst, uint32_t field, uint64_t value)
//{
//	uint32_t idx = (inst*p_acc->field_cnt) + field;
//	uint64_t v = value - (*(p_acc->old_value+idx));
//	*(p_acc->interval_value+idx) = v;
//	*(p_acc->old_value+idx) = value;
//	return 0;
//}

/**
 * @ingroup np_stats_acc
 * @brief get the last generated value
 *
 * @param[in] p_acc 	        acc struct
 * @param[in] inst 				instance index
 * @param[in] field 	    	field index
 * @return the last generated value, if no value has been generated, 0 is returned
 *
 * the caller should call this function periodically with a new value each time.
 * the accumulator will then use the new value to generate the value the caller needed.
 * In such case, the caller should call this function once per instance per field with the existing value.
 **/
//static inline uint64_t np_stats_acc_get_val(np_stats_acc* p_acc, uint32_t inst, uint32_t field)
//{
//	switch(p_acc->type)
//	{
//	case NP_STATS_ACC_VAL_INTERVAL:
//		return *(p_acc->interval_value+(inst*p_acc->field_cnt + field));
//	case NP_STATS_ACC_VAL_CURRENT:
//		return *(p_acc->old_value+(inst*p_acc->field_cnt + field));
//	}
//	return 0;
//}

#define np_stats_acc_get_val_def(data_type) \


np_stats_acc_get_val_def(float);
np_stats_acc_get_val_def(double);
np_stats_acc_get_val_def(uint32_t);
np_stats_acc_get_val_def(uint64_t);

#define np_stats_acc_type_def(data_type, name_type) \
static inline void np_stats_acc_cal_acc_## name_type ( np_stats_acc* p_acc, uint32_t inst, uint32_t field, data_type new_value) \
{\
	uint32_t idx = (inst*p_acc->field_cnt) + field; \
	np_stats_acc_type acc_type = p_acc->acc_types[field]; \
	np_stats_acc_unit* p_unit = p_acc->acc_data + idx;\
	np_stats_acc_distribute* p_dis = p_acc->acc_dis + idx; \
	data_type* p_raw = (data_type*)(p_unit->raw_data); \
	data_type* p_ret = (data_type*)(p_unit->acc_data); \
	p_unit->count++; \
	if(p_unit->count==1) { \
		*p_raw = new_value; \
		*p_ret = (*p_raw); \
	} \
	else \
	{ \
		switch(acc_type) \
		{\
		case 	NP_STATS_ACC_UNK:  \
			printf("unknow acc type"); \
			break; \
		case 	NP_STATS_ACC_MAX:  \
			if(*p_raw < new_value)	{ \
				*p_raw = new_value; \
			} \
			*p_ret = (*p_raw); \
			break; \
		case 	NP_STATS_ACC_MIN: \
			if(*p_raw > new_value)	{ \
				*p_raw = new_value; \
			} \
			*p_ret = (*p_raw); \
			break; \
		case 	NP_STATS_ACC_MEAN: \
			*p_raw += new_value; \
			*p_ret = (*p_raw) / p_unit->count; \
			break; \
		case 	NP_STATS_ACC_SUM: \
			*p_raw += new_value; \
			*p_ret = (*p_raw); \
			break; \
		case	NP_STATS_ACC_LAST:\
		     *p_raw = new_value; \
		     *p_ret = (*p_raw); \
		     break;\
		case	NP_STATS_ACC_DIS:\
		     np_stats_acc_dis_insert_elem_ ## name_type(p_dis,new_value); \
		     break;\
		} \
	}\
\
}\
static inline void np_stats_acc_push_## name_type(np_stats_acc* p_acc, uint32_t inst, uint32_t field, data_type value) \
{ \
	uint32_t idx = (inst*p_acc->field_cnt) + field; \
	data_type v = value;\
	if(p_acc->type==NP_STATS_ACC_VAL_INTERVAL)\
	{ /*interval, we should calculate the interval value here*/\
		if(p_acc->interval_init[idx]==0) \
		{/*one time init for a single field*/\
			p_acc->interval_init[idx] = 1;\
			/*initialize old_value, and not calling the cal_acc functions*/ \
			*NP_PTR_OFFSET(p_acc->old_value, idx*sizeof(uint64_t), data_type) = value; \
			return; \
		}\
		v = value - (*NP_PTR_OFFSET(p_acc->old_value, idx*sizeof(uint64_t), data_type)); \
	} \
	*NP_PTR_OFFSET(p_acc->old_value, idx*sizeof(uint64_t), data_type) = value; \
	np_stats_acc_cal_acc_## name_type(p_acc, inst, field, v); \
\
	return; \
} \
static inline data_type np_stats_acc_get_val_##name_type (np_stats_acc* p_acc, uint32_t inst, uint32_t field) \
{ \
	void* p_data = (p_acc->acc_data+(inst*p_acc->field_cnt + field))->acc_data; \
		return *(data_type*)p_data;\
} \
static inline void np_stats_acc_print_result_## name_type(np_stats_acc* p_acc,uint16_t prnt_width) \
{ \
	uint32_t instance_cnt; \
	char pnt_str[10] = {0}; \
    NP_STAT_FIELD_PRINT_FORMAT(pnt_str,prnt_width,string); \
	char pnt_uint64[10] = {0}; \
    NP_STAT_FIELD_PRINT_FORMAT(pnt_uint64,prnt_width,uint64); \
    char pnt_name_type[10] = {0}; \
    NP_STAT_FIELD_PRINT_FORMAT(pnt_name_type,prnt_width,name_type); \
	for(instance_cnt = 0;instance_cnt < p_acc->instance_cnt;instance_cnt++) \
	{ \
		printf("-------------------------------------------------------------------------------------------------------------------------------\n");  \
		printf(pnt_str,"count"); \
		uint32_t inst_end = instance_cnt * p_acc->field_cnt;  \
		uint32_t field_cnt; \
		for(field_cnt = 0;field_cnt < p_acc->field_cnt;field_cnt++) \
		{ \
			switch(p_acc->acc_types[field_cnt]) \
			{ \
				case NP_STATS_ACC_UNK: \
					printf(pnt_str,"unknow"); \
					break;\
				case NP_STATS_ACC_MAX: \
				    printf(pnt_str,"max");\
				    break;\
				case NP_STATS_ACC_MIN:\
					printf(pnt_str,"min");\
					break;\
				case NP_STATS_ACC_MEAN:\
					printf(pnt_str,"average");\
					break;\
				case NP_STATS_ACC_SUM:\
					printf(pnt_str,"total");\
					break;\
				case NP_STATS_ACC_LAST:\
					printf(pnt_str,"last");\
					break;\
				case NP_STATS_ACC_DIS:\
				     break;\
			}\
		}\
		printf("|\n");\
		printf(pnt_uint64,p_acc->acc_data[inst_end * p_acc->field_cnt].count);\
		for(field_cnt = 0;field_cnt < p_acc->field_cnt;field_cnt++)\
		{\
			data_type value = np_stats_acc_get_val_## name_type(p_acc,instance_cnt,field_cnt);\
			switch(p_acc->acc_types[inst_end + field_cnt])\
			{\
			    case NP_STATS_ACC_UNK:\
					printf(pnt_str,"0");\
					break;\
				case NP_STATS_ACC_MAX:\
			    case NP_STATS_ACC_MIN:\
				case NP_STATS_ACC_MEAN:\
				case NP_STATS_ACC_SUM:\
				case NP_STATS_ACC_LAST:\
					printf(pnt_name_type,value);\
					break;\
				case NP_STATS_ACC_DIS:\
				     break;\
			}\
		}\
		printf("|\n");\
		printf("-------------------------------------------------------------------------------------------------------------------------------\n\n");\
	}\
}

static inline void np_stats_acc_clear(np_stats_acc* p_acc)
{
	memset(p_acc->acc_data, 0, sizeof(np_stats_acc_unit)*p_acc->instance_cnt*p_acc->field_cnt);
}

static inline uint32_t np_stats_acc_get_field_cnt(np_stats_acc* p_acc)
{
	return p_acc->field_cnt;
}

static inline uint32_t np_stats_acc_get_instance_cnt(np_stats_acc* p_acc)
{
	return p_acc->instance_cnt;
}

np_stats_acc_type_def(float, float);
np_stats_acc_type_def(double, double);
np_stats_acc_type_def(uint32_t, uint32);
np_stats_acc_type_def(int32_t, int32);
np_stats_acc_type_def(int64_t, int64);
np_stats_acc_type_def(uint64_t, uint64);


#ifdef __cplusplus
}
#endif

#endif /* NP_STATS_H_ */
